/*
 * JsonRpcController - a abstract controller for Spring and JSON-RPC-JAVA integration
 * 
 * Copyright Aldrin Edison Baroi 2006
 * 
 * Aldrin Edison Baroi <aldrin91011@yahoo.com>
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under 
 * the License.
 * 
 */
package org.charless.spring.jsonrpc;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.servlet.Servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;

import com.metaparadigm.jsonrpc.JSONRPCBridge;
import com.metaparadigm.jsonrpc.JSONRPCServlet;

/**
 * <p>
 * <b>Implementation Notes:</b><br/><br/>
 * <ul>
 * <li>Frist create the interface classes for the AJAX services to be exposed.</li>
 * <li>Create the controller by extending <b>JsonRpcController</b> that
 * implements the AJAX service interfaces.</li>
 * <li>Configure the controller as described below in the 'Configuration
 * Options' section.</li>
 * </ul>
 * </p>
 * <p>
 * <b>Configuration Options</b><br/><br/> <b>Option 1:</b> (Exposing only
 * one service interface)<br/>
 * 
 * <pre>
 *        &lt;bean name=&quot;[Controller Name]&quot; class=&quot;[Controller Class]&quot;&gt;
 *          &lt;property name=&quot;ajaxServiceName&quot; value=&quot;[Ajax Service Name]&quot; /&gt;
 *          &lt;property name=&quot;ajaxServiceInterface&quot; value=&quot;[Ajax Service Interface]&quot; /&gt;
 *        &lt;/bean&gt; 
 * </pre>
 * 
 * <b>Option 2:</b> (Exposing more than one service interfaces)<br/>
 * 
 * <pre>
 *        &lt;bean name=&quot;[Controller Name]&quot; class=&quot;[Controller Class]&quot;&gt;
 *          &lt;property name=&quot;ajaxServices&quot;&gt;
 *            &lt;map&gt;
 *              &lt;entry key=&quot;[Ajax Service Name 1]&quot; value=&quot;[Ajax Service Interface 1]&quot; /&gt;
 *              &lt;entry key=&quot;[Ajax Service Name 2]&quot; value=&quot;[Ajax Service Interface 2]&quot; /&gt;
 *            &lt;/map&gt;
 *          &lt;/property&gt;
 *        &lt;/bean&gt;
 * </pre>
 * 
 * </p>
 * 
 * @author Aldrin E Baroi
 * 
 */
public abstract class JsonRpcController extends AbstractController implements
		DisposableBean, InitializingBean, BeanNameAware
{
	private Log logger = LogFactory.getLog(JsonRpcController.class);
	private ThreadLocal<HttpServletRequest>  httpRequest  = new ThreadLocal<HttpServletRequest>();
	private ThreadLocal<HttpServletResponse> httpResponse = new ThreadLocal<HttpServletResponse>();
	private String jsonRpcControllerName;
	private Servlet jsonRpcServlet;
	private String ajaxServiceName;
	private Class<?> ajaxServiceInterface;
	private Map<String,String> ajaxServices; // Map<serviceProviderName,serviceProviderClassName>
	private Map<String,Class<?>> ajaxServicesList;// Map<serviceProviderName,serviceProviderInterface>
	private boolean ajaxServicesRegistered;

	/**
	 * 
	 */
	public JsonRpcController()
	{
		super();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.springframework.web.servlet.mvc.AbstractController#handleRequestInternal(javax.servlet.http.HttpServletRequest,
	 *      javax.servlet.http.HttpServletResponse)
	 */
	protected final ModelAndView handleRequestInternal(HttpServletRequest req,
			HttpServletResponse res) throws Exception
	{
		httpRequest.set(req);
		httpResponse.set(res);
		if (!ajaxServicesRegistered) {
			// Register to global bridge
			registerAjaxServices(req);
		}
		HttpSession session = req.getSession();
		JSONRPCBridge json_bridge = null;
		json_bridge = (JSONRPCBridge) session.getAttribute("JSONRPCBridge");
		if (json_bridge == null) {
			json_bridge = new JSONRPCBridge();
			session.setAttribute("JSONRPCBridge",json_bridge);
			if (logger.isDebugEnabled()) {
				logger.debug("A new local JSONRPCBridge \'"+json_bridge.toString()+"\' has been created for session with id="+session.getId());
			}
			registerSessionAjaxServices(json_bridge);
		}
		
		jsonRpcServlet.service(req, res);
		return null;
	}

	/**
	 * 
	 * @param req
	 * @param res
	 */
	private final void registerAjaxServices(HttpServletRequest req)
	{
		if (logger.isDebugEnabled())
		{
			logger.debug("Registering: Ajax Services for ["
					+ jsonRpcControllerName + "].");
		}
		// Using global bridge
		JSONRPCBridge j = JSONRPCBridge.getGlobalBridge();
		for (Iterator<String> i = ajaxServicesList.keySet().iterator(); i.hasNext();)
		{
			String   _serviceProviderName = /*(String)*/ i.next();
			Class<?> _serviceProviderInterface = /*(Class)*/ ajaxServicesList
					.get(_serviceProviderName);
			j.registerObject(_serviceProviderName, this,
					_serviceProviderInterface);
		}
		ajaxServicesRegistered = true;
		if (logger.isDebugEnabled())
		{
			logger.debug("Registered : Ajax Services for ["
					+ jsonRpcControllerName + "].");
		}
		
	}
	
	private final void registerSessionAjaxServices(JSONRPCBridge bridge) throws Exception
	{
		if (logger.isDebugEnabled()) {
			logger.debug("Registering: Ajax Services to Session bridge \'"+bridge.toString()+"\'");
		}
		registerSessionAjaxServicesImpl(bridge);
	}
	
	protected void registerSessionAjaxServicesImpl(JSONRPCBridge bridge) throws Exception {
		
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.springframework.beans.factory.DisposableBean#destroy()
	 */
	public void destroy() throws Exception
	{
		jsonRpcServlet.destroy();
		jsonRpcServlet = null;
	}

	/**
	 * 
	 */
	public final void setBeanName(String beanName)
	{
		this.jsonRpcControllerName = beanName;
	}

	/**
	 * 
	 * @param serviceProviderName
	 */
	public final void setAjaxServiceName(String serviceProviderName)
	{
		this.ajaxServiceName = serviceProviderName;
	}

	/**
	 * 
	 * @param serviceProviderInterface
	 */
	public final void setAjaxServiceInterface(Class<?> serviceProviderInterface)
	{
		this.ajaxServiceInterface = serviceProviderInterface;
	}

	/**
	 * 
	 * @param serviceProviders
	 */
	public final void setAjaxServices(Map<String,String> serviceProviders)
	{
		this.ajaxServices = serviceProviders;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
	 */
	public final void afterPropertiesSet() throws Exception
	{
		if (this.ajaxServiceName == null && this.ajaxServiceInterface == null
				&& this.ajaxServices == null)
		{
			IllegalArgumentException e = new IllegalArgumentException(
					"Either ajaxServiceName and ajaxServiceInterface or, ajaxServices must be set.");
			logger.fatal(e);
			throw e;
		}
		this.ajaxServicesList = new HashMap<String,Class<?>>();
		if (this.ajaxServices == null)
		{
			if (this.ajaxServiceName == null
					|| this.ajaxServiceInterface == null)
			{
				IllegalArgumentException e = new IllegalArgumentException(
						"Both ajaxServiceName and ajaxServiceInterface must be set.");
				logger.fatal(e);
				throw e;
			}
			if (!this.ajaxServiceInterface.isInterface())
			{
				IllegalArgumentException e = new IllegalArgumentException("["
						+ ajaxServiceInterface.getName() + "] "
						+ "ajaxServiceInterface must be of type interface.");
				logger.fatal(e);
				throw e;
			}
			this.ajaxServicesList.put(this.ajaxServiceName,
					this.ajaxServiceInterface);
		}
		else
		{
			// heu ! pas tres optimise ce code !
			for (Iterator<String> i = ajaxServices.keySet().iterator(); i.hasNext();)
			{
				String _serviceProviderName = /*(String)*/ i.next();
				String _serviceProviderClassName = /*(String)*/ ajaxServices
						.get(_serviceProviderName);
				Class<?> _serviceProviderInterface = Class
						.forName(_serviceProviderClassName);
				if (!_serviceProviderInterface.isInterface())
				{
					throw new IllegalArgumentException("["
							+ _serviceProviderClassName + "] "
							+ "ajaxServiceInterface must be of type interface.");
				}
				this.ajaxServicesList.put(_serviceProviderName,
						_serviceProviderInterface);
			}
		}
		JsonRpcServletContext ctx = new JsonRpcServletContext(
				getServletContext());
		this.jsonRpcServlet = (Servlet) JSONRPCServlet.class.newInstance();
		this.jsonRpcServlet.init(ctx.getJsonRpcServletConfg());
	}

	public final HttpServletRequest getHttpServletRequest()
	{
		return (HttpServletRequest) httpRequest.get();
	}

	public final HttpServletResponse getHttpServletResponse()
	{
		return (HttpServletResponse) httpResponse.get();
	}
}
